import { DynamicModule, Global, Module, Provider } from '@nestjs/common'
import { USERS_CLIENT, USERS_CLIENT_OPTIONS } from './users-client.constants'
import { 
  UsersClientModuleAsyncOptions, 
  UsersClientModuleOptions, 
  UsersClientModuleOptionsFactory,
} from './interfaces/users-client-options.interface'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { UsersClientService } from './users-client.service'

@Module({
  imports: [
    ClientsModule.registerAsync([
      {
        name: USERS_CLIENT,
        inject: [ USERS_CLIENT_OPTIONS ],
        useFactory: (options: UsersClientModuleOptions) => ({
          transport: Transport.NATS,
          options: {
            servers: options.natsServers,
          },
        }),
      },
    ]),
  ],
  providers: [ UsersClientService ],
  exports: [ UsersClientService ],
})
@Global()
export class UsersClientModule {
  public static forRoot(config: UsersClientModuleOptions): DynamicModule {
    return {
      module: UsersClientModule,
      providers: [
        {
          provide: USERS_CLIENT_OPTIONS,
          useValue: config,
        },
      ],
      exports: [ USERS_CLIENT_OPTIONS ],
    }
  }

  public static forRootAsync(options: UsersClientModuleAsyncOptions): DynamicModule {
    return {
      module: UsersClientModule,
      imports: options.imports,
      providers: this.createAsyncProviders(options),
      exports: [ USERS_CLIENT_OPTIONS ],
    }
  }

  private static createAsyncProviders(options: UsersClientModuleAsyncOptions): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [ this.createAsyncOptionsProvider(options) ]
    }

    return [
      this.createAsyncOptionsProvider(options),
      {
        useClass: options.useClass!,
        provide: options.useClass!,
      },
    ]
  }

  private static createAsyncOptionsProvider(options: UsersClientModuleAsyncOptions): Provider {
    if (options.useFactory) {
      return {
        provide: USERS_CLIENT_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      }
    }

    return {
      provide: USERS_CLIENT_OPTIONS,
      useFactory: async (optionsFactory: UsersClientModuleOptionsFactory) => (
        await optionsFactory.createUsersClientOptions()
      ),
      inject: [ (options.useExisting || options.useClass)! ],
    }
  }
}